package com.hmdp.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.bean.copier.CopyOptions;
import cn.hutool.core.lang.UUID;
import cn.hutool.core.util.RandomUtil;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.hmdp.dto.LoginFormDTO;
import com.hmdp.dto.Result;
import com.hmdp.dto.UserDTO;
import com.hmdp.entity.User;
import com.hmdp.mapper.UserMapper;
import com.hmdp.service.IUserService;
import com.hmdp.utils.RedisConstants;
import com.hmdp.utils.RegexUtils;
import com.hmdp.utils.SystemConstants;
import com.hmdp.utils.UserHolder;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.connection.BitFieldSubCommands;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import javax.servlet.http.HttpSession;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.function.BiFunction;

/**
 * <p>
 * 服务实现类
 * </p>
 *
 * @author 虎哥
 * @since 2021-12-22
 */
@Service
@RequiredArgsConstructor
@Slf4j
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
    private final StringRedisTemplate stringRedisTemplate;

    /**
     * 发送验证码
     * @param phone
     * @param session
     * @return
     */
    public Result sendCode(String phone, HttpSession session) {
        // 1.校验手机号
        if(RegexUtils.isPhoneInvalid(phone))
            return Result.fail("手机号格式错误！");
        // 2.生成验证码
        String code = RandomUtil.randomNumbers(6);
        // 3.保存验证码到redis，同时设置有效期
        stringRedisTemplate.opsForValue().set(RedisConstants.LOGIN_CODE_KEY + phone, code, RedisConstants.LOGIN_CODE_TTL, TimeUnit.MINUTES);
        // 4.(模拟)发送验证码
        log.debug("发送短信验证码成功，验证码：{}", code);
        return Result.ok();
    }

    /**
     * 登录功能
     * @param loginForm 登录参数
     */
    public Result login(LoginFormDTO loginForm, HttpSession session) {
        // 1.校验手机号
        String phone = loginForm.getPhone();
        if(RegexUtils.isPhoneInvalid(phone))
            return Result.fail("手机号格式错误");
        // 2.从redis获取验证码，key=LOGIN_CODE_KEY + phone
        String code = loginForm.getCode();
        String cacheCode = stringRedisTemplate.opsForValue().get(RedisConstants.LOGIN_CODE_KEY + phone);
        if(cacheCode == null || !cacheCode.equals(code))
            return Result.fail("验证码错误");
        // 3.查询用户是否存在
        User user = lambdaQuery().eq(User::getPhone, phone).one();
        if(user == null) {
            // 4.用户不存在，创建新用户
            user = new User();
            user.setPhone(phone);
            user.setNickName(SystemConstants.USER_NICK_NAME_PREFIX + RandomUtil.randomString(10));
            save(user);
        }
        // 5.保存用户信息到redis
        // 5.1 token返回给前端，在登录校验时再结合为tokenKey从redis拿到用户信息
        String token = UUID.randomUUID().toString(true);
        // 5.2 tokenKey是用户信息保存到redis的健
        String tokenKey = RedisConstants.LOGIN_USER_KEY + token;
        // 5.3 将user对象转换为userDTO，再转换为Map，重要知识点
        UserDTO userDTO = BeanUtil.copyProperties(user, UserDTO.class);
        Map<String, Object> userMap = BeanUtil.beanToMap(userDTO, new HashMap<>(), 
                CopyOptions.create().ignoreNullValue()
                        .setFieldValueEditor(new BiFunction<String, Object, Object>() {
                            @Override
                            public Object apply(String fieldName, Object fieldValue) {
                                return fieldValue.toString();
                            }
                        })
        );
        // 5.4 将Map保存到redis中
        stringRedisTemplate.opsForHash().putAll(tokenKey, userMap);
        // 5.5 设置用户信息在redis的过期时间
        stringRedisTemplate.expire(tokenKey, RedisConstants.LOGIN_USER_TTL, TimeUnit.SECONDS);
        // 6.返回token
        return Result.ok(token);
    }

    @Override
    public Result sign() {
        // 获取用户id
        Long userId = UserHolder.getUser().getId();
        // 获取当前日期
        LocalDateTime now = LocalDateTime.now();
        String nowStr = now.format(DateTimeFormatter.ofPattern("yyyy:MM"));
        // 获取今天是当前月的第几天
        int day = now.getDayOfMonth();
        // 拼接key
        String key = RedisConstants.USER_SIGN_KEY + userId + ":" + nowStr;

        /*// 存入redis，offset从0开始，每月的第1天对应的offset为0，所以需要day-1
        Boolean isSuccess = stringRedisTemplate.opsForValue().setBit(key, day - 1, true);
        return BooleanUtil.isTrue(isSuccess) ? Result.ok() : Result.fail("签到失败，请重试");*/

        stringRedisTemplate.opsForValue().setBit(key, day - 1, true);
        return Result.ok();
    }

    @Override
    public Result signCount() {
        // 获取用户id
        Long userId = UserHolder.getUser().getId();
        // 获取当前日期
        LocalDateTime now = LocalDateTime.now();
        String nowStr = now.format(DateTimeFormatter.ofPattern("yyyy:MM"));
        // 获取今天是当前月的第几天
        int day = now.getDayOfMonth();
        // 拼接key
        String key = RedisConstants.USER_SIGN_KEY + userId + ":" + nowStr;
        // 获取本月截止今天为止的所有的签到记录(返回的是一个十进制的数字)，为什么这里返回结果是一个集合？因为bitfield支持批量操作，包括查询、新增等
        List<Long> list = stringRedisTemplate.opsForValue().bitField(  // BITFIELD key get type offset
                key,
                BitFieldSubCommands.create()
                        .get(BitFieldSubCommands.BitFieldType.unsigned(day))  // get type，type=uday
                        .valueAt(0) // offset=0
        );
        if(list == null || list.isEmpty()) {
            return Result.ok(0);
        }
        // 拿到十进制数字
        Long num = list.get(0);
        // 遍历该数字的每一位，与1进行与运算
        int cnt = 0; // 统计1的个数,即连续签到次数
        while ((num & 1) == 1) {
            cnt++;
            num >>>= 1;   // 无符号左移一位
        }
        return Result.ok(cnt);
    }
}